#include "MusinkSubmitter.h"
//#include "MusinkSong.h"
//#include "EncodingUtils.h"		//Will be added if needed (text encoding conversion)

#include <windows.h>
#include <process.h>

#include <iostream>
#include <sstream>
#include <string>

#undef STRICT

/******************************************************************************
    Constants
******************************************************************************/
static const char* myVersion = "0.0.1";
static const char* myHost = "localhost";
static const int   myDefaultPort = 34567;
static const int   myWait = 60000; // 60s in ms


MusinkSubmitter::MusinkSubmitter()
{
	string ver = "Musink";
	while (ver.find_first_of("Musink")==-1  && myPort < myDefaultPort+100 )
	{
		if ( myPort>myDefaultPort) mySock.Disconnect();	//Disconnect sock if we are already connected

		myPort = mySock.Connect(myHost,myDefaultPort);  //Connect to Musink Database Manager
	
		//Code to check that we're connected to MuSink
		mySock.Send("version");
		mySock.Receive(ver);

	}


	mySock.Send("marco");		//Get Plugin ID
	mySock.Receive(ver);

	myPluginID = ver.substr(4);

}

MusinkSubmitter::MusinkSubmitter(string id)
{
	string ver = "Musink";
	while (ver.find_first_of("Musink")==-1  && myPort < myDefaultPort+100 )
	{
		if ( myPort>myDefaultPort) mySock.Disconnect();	//Disconnect sock if we are already connected
		myPort = mySock.Connect(myHost,myDefaultPort);  //Connect to Musink Database Manager
	
		mySock.Send("version");
		mySock.Receive(ver);
	}
	myPluginID = id;
}


MusinkSubmitter::~MusinkSubmitter()
{
	mySock.Disconnect();		//Disconnect from the Database Manager
}

string MusinkSubmitter::getID()
{
	return myPluginID;
}

MusinkSong MusinkSubmitter::getSong(const string & filename)
{
	string songDataString;
	//get <filename>
	mySock.Send("get \"" + escape(filename) + "\"");
	mySock.Receive(songDataString);  //Get all data in the Database Manager
	vector<string> var = splitVariables(songDataString);	//Split up the String from the database manager into variables

	if (var.size() != 9)
	{
		ostringstream err;
		err<<"Musink Database Manager sent incorrect number of parameters. Expected 9, received "<<var.size();
		err<<"\n\t"<<songDataString;
		throw MusinkException(err.str());
	}

	/*
	Format:
		var[0] filepath
		var[1] artist 
		var[2] album 
		var[3] title
		var[4] duration //needs atoi
		var[5] hash
		var[6] playcount	//needs atoi
		var[7] rating		//needs atof
		var[8] last_played
	*/

	return MusinkSong(var[0],var[1],var[2],var[3],atoi(var[4].c_str()),var[5],atoi(var[6].c_str()),atof(var[7].c_str()),var[8]);

}

MusinkSong MusinkSubmitter::getSongByHash(const string & hash)
{
	string songDataString;
	mySock.Send("geth \"" + escape(hash) + "\"");
	mySock.Receive(songDataString);  //Get all data in the Database Manager
	vector<string> var = splitVariables(songDataString);	//Split up the String from the database manager into variables

	if (var.size() != 9)
	{
		ostringstream err;
		err<<"Musink Database Manager sent incorrect number of parameters. Expected 9, received "<<var.size();
		err<<"\n\t"<<songDataString;
		throw MusinkException(err.str());
	}

	/*
	Format:
		var[0] filepath
		var[1] artist 
		var[2] album 
		var[3] title
		var[4] duration //needs atoi
		var[5] hash
		var[6] playcount	//needs atoi
		var[7] rating		//needs atof
		var[8] last_played
	*/

	return MusinkSong(var[0],var[1],var[2],var[3],atoi(var[4].c_str()),var[5],atoi(var[6].c_str()),atof(var[7].c_str()),var[8]);
}

void MusinkSubmitter::updateSong(const MusinkSong & song)
{
	string sendStr="set \"" + myPluginID+"\" \"";
	//Escape individual variables before sending
	sendStr+=(escape(song.filePath)+"\" \""+escape(song.artist)+"\" \""+escape(song.album)+"\" \""+escape(song.title)+"\" \"");
	//Nothing below this should need escaping
	sendStr+=(song.duration);
	sendStr+=("\" \""+song.hash+"\" \"");
	sendStr+=(song.playCount);
	sendStr+="\" \"";
	sendStr+=song.rating;
	sendStr+="\" \""+song.lastPlayed+"\"";
	
	mySock.Send(sendStr);		//Send to Database Manager
	string recvStr;
	mySock.Receive(recvStr);	//Get the message response
	
	if( !recvStr.find_first_of("OK") !=0)		//Make sure the database manager did update it
	{
		ostringstream err;
		err<<"Musink Database Manager did not update song correctly: "<<song.filePath<<"\r\n";
		throw MusinkException(err.str());
	}
}

void MusinkSubmitter::getChanges(vector<MusinkSong> &lib)
{
	mySock.Send("startup " +myPluginID);
	string recvStr;
	mySock.Receive(recvStr);	//I'm assuming here that the DBM is sending me in the format "hash1" "hash2" etc.

	vector<string> files = splitVariables(recvStr); //Split up into filenames

	for (unsigned int i=0; i<files.size(); ++i)
	{
		lib.push_back(getSongByHash(files[i]));
	}

	
}

	/*
		Player library goes into &library, hash field is empty for each song.

		Submitter sends each Song to the DatabaseManager
			If Manager responds 'OK':
				Remove from &library 
				(Manager returns OK if rating is 0.0 or Matches, playCounts are added, and lastPlayed is whichever is newer)
			Else:
				Current Song gets filled with hash
				Make copy of Current Song
					Fill with variables from Manager (rating, playcount, last_played), 
				Add copy to &conflicts

		Plugin then does the following
			Works through each of the conflicts, asking the user to choose which rating it wants
	*/

void MusinkSubmitter::syncDatabases(vector<MusinkSong> &library, vector<MusinkSong> &conflicts)
{
	//Before running this, MusinkSubmitter.SongCollection should be run on file names.
	vector<MusinkSong> tempLib;
	MusinkSong song;
	for (unsigned int i=0; i<library.size(); ++i)
	{
		song=library[i];
		string sendStr="new \"" + myPluginID+"\" \"";
		//Escape individual variables before sending
		sendStr+=(escape(song.filePath)+"\" \""+escape(song.artist)+"\" \""+escape(song.album)+"\" \""+escape(song.title)+"\" \"");
		//Nothing below this should need escaping
		sendStr+=(song.duration);
		sendStr+=("\" \""+song.hash+"\" \"");
		sendStr+=(song.playCount);
		sendStr+="\" \"";
		sendStr+=song.rating;
		sendStr+="\" \""+song.lastPlayed+"\"";
	
		mySock.Send(sendStr);		//Send to Database Manager
		string recvStr;
		mySock.Receive(recvStr);	//Get the message response

		if (recvStr.find_first_of("OK")!=0)
		{
			tempLib.push_back(song);

			if (recvStr.find_first_of("conflict"))
			{
				ostringstream err;
				err<<"Musink Database Manager did not give us back a correct response: \r\n\t"<<recvStr<<"\r\n";
				throw MusinkException(err.str());
			}
			song.rating = atof(recvStr.substr(8).c_str());	//convert recvStr after 'conflict ' to a double
			conflicts.push_back(song);
		}
	}
	library = tempLib;
}

vector<MusinkSong> MusinkSubmitter::songCollection(const vector<string> & files)
{
	vector <MusinkSong> lib;
	for (unsigned int i=0; i<files.size(); ++i)
	{
		lib.push_back(getSong(files[i]));
	}
	return lib;
}



//////////////////////////////////////////////////////
//Private, Internal Functions                       //
//////////////////////////////////////////////////////

vector<string> MusinkSubmitter::splitVariables(string &inString)
{
	//Not very fault tolerant (ie there NumQuotes must be %2), but hopefully the Database Manager will not send us garbage.

	vector<string> var;
	string tempWord = "";
	bool firstQuote = true;
	unsigned int i=0;
	while (i<inString.size() && inString[i]!='\"') i++;	//Check that we start with a quote;
	
	
	for (; i<inString.size(); ++i)
	{
		if (inString[i]=='\\')		//if character is a '\', we're escaping a quote (probably), 
			++i;					//and thus want to skip the next character as well
									//Note: All '\' characters must be escaped on the Database Manager side
		else if (inString[i]=='\"')	//If we hit a quote
		{
			if(firstQuote)			//If we already hit the first quote, and are now hitting a second
			{
				
				var.push_back(unescape(tempWord));	//Add the word
				tempWord = "";				//reset tempWord
				firstQuote = false;			//reset the firstQuote found flag to false
			}
			else
			{
				firstQuote = true;			//else if this is the 'first quote' of a variable, we'll set the flag to true
			}

		}
		else
			tempWord.push_back(inString[i]);	//If this character is not a \ or a ", 
												//we're going to push it back into the 'tempWord'
	}

	return var;
}

string MusinkSubmitter::unescape(const string &str)
{
	string temp;

	for (unsigned int i=0; i<str.size(); ++i)
	{
		if (str[i]=='\\')
			++i;
		else
			temp.push_back(str[i]);
	}
	return temp;
}

string MusinkSubmitter::escape(const string &str)
{
	string temp;

	for (unsigned int i=0; i<str.size(); ++i)
	{
		//escape \, ", \',
		if (str[i]=='\\'||str[i]=='\"'||str[i]=='\'')	
			temp.push_back('\\'); 
		
		temp.push_back(str[i]);
	}

	return temp;

}